#pragma once

#include <chrono>
#include <ctime>
#include <cstdint>
#include <initializer_list>
#include "event/event_object.hh"

namespace kratos { namespace corelib {

class EventQueue;

// Enumerations for week day
enum class Weekday : uint8_t {
    Sunday = 0, // SUNDAY
    Monday, // MONDAY
    Tuesday, // TUESDAY
    Wednesday, // WEDNESDAY
    Thursday, // THURSDAY
    Friday, // FRIDAY
    Saturday, // SATURDAY
};

// Day time info.
struct Datetime {
    int year; // Year
    int month; // Month
    int day; // Day
    Weekday wday; // week day @see WeekDay
    int hour; // Hour
    int minute; // Minute
    int second; // Second
    // ctor
    Datetime();
    // ctor
    // @param t timestamp in second
    Datetime(std::time_t t);
    // ctor
    // @param fmt like 1970/01/01 00:00:00
    Datetime(const std::string& fmt);
    // ctor
    // @param params the integer sequence like: year, month, day, hour, minute, second
    Datetime(const std::initializer_list<int>& params);
    // returns timestamp in second(CIVIL)
    std::time_t getTimestamp();
    // returns timestamp in second(UTC)
    std::time_t getTimestampUTC();
    // returns human readable string, like 1970/01/01 00:00:00
    std::string getDateString();
};

// Time utilities
struct TimeUtil {
    ///////////////////////////////////////////////////////////////////////////
    //// copy from http://howardhinnant.github.io/date_algorithms.html
    ///////////////////////////////////////////////////////////////////////////

    // Returns number of days since civil 1970-01-01.  Negative values indicate
    //    days prior to 1970-01-01.
    // Preconditions:  y-m-d represents a date in the civil (Gregorian) calendar
    //                 m is in [1, 12]
    //                 d is in [1, last_day_of_month(y, m)]
    //                 y is "approximately" in
    //                   [numeric_limits<Int>::min()/366, numeric_limits<Int>::max()/366]
    //                 Exact range of validity is:
    //                 [civil_from_days(numeric_limits<Int>::min()),
    //                  civil_from_days(numeric_limits<Int>::max()-719468)]    
    template <class Int>
    static Int days_from_civil(Int y, unsigned m, unsigned d) noexcept
    {
        static_assert(std::numeric_limits<unsigned>::digits >= 18,
            "This algorithm has not been ported to a 16 bit unsigned integer");
        static_assert(std::numeric_limits<Int>::digits >= 20,
            "This algorithm has not been ported to a 16 bit signed integer");
        y -= m <= 2;
        const Int era = (y >= 0 ? y : y - 399) / 400;
        const unsigned yoe = static_cast<unsigned>(y - era * 400);      // [0, 399]
        const unsigned doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;  // [0, 365]
        const unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;         // [0, 146096]
        return era * 146097 + static_cast<Int>(doe) - 719468;
    }

    // Returns year/month/day triple in civil calendar
    // Preconditions:  z is number of days since 1970-01-01 and is in the range:
    //                   [numeric_limits<Int>::min(), numeric_limits<Int>::max()-719468].
    template <class Int>
    static std::tuple<Int, unsigned, unsigned> civil_from_days(Int z) noexcept
    {
        static_assert(std::numeric_limits<unsigned>::digits >= 18,
            "This algorithm has not been ported to a 16 bit unsigned integer");
        static_assert(std::numeric_limits<Int>::digits >= 20,
            "This algorithm has not been ported to a 16 bit signed integer");
        z += 719468;
        const Int era = (z >= 0 ? z : z - 146096) / 146097;
        const unsigned doe = static_cast<unsigned>(z - era * 146097);          // [0, 146096]
        const unsigned yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;  // [0, 399]
        const Int y = static_cast<Int>(yoe) + era * 400;
        const unsigned doy = doe - (365 * yoe + yoe / 4 - yoe / 100);                // [0, 365]
        const unsigned mp = (5 * doy + 2) / 153;                                   // [0, 11]
        const unsigned d = doy - (153 * mp + 2) / 5 + 1;                             // [1, 31]
        const unsigned m = mp + (mp < 10 ? 3 : -9);                            // [1, 12]
        return std::tuple<Int, unsigned, unsigned>(y + (m <= 2), m, d);
    }


    // Returns: true if y is a leap year in the civil calendar, else false
    template <class Int>
    static bool is_leap(Int y) noexcept
    {
        return  y % 4 == 0 && (y % 100 != 0 || y % 400 == 0);
    }

    // Preconditions: m is in [1, 12]
    // Returns: The number of days in the month m of common year
    // The result is always in the range [28, 31].
    static unsigned last_day_of_month_common_year(unsigned m) noexcept
    {
        constexpr static unsigned char a[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
        return a[m - 1];
    }


    // Preconditions: m is in [1, 12]
    // Returns: The number of days in the month m of leap year
    // The result is always in the range [29, 31].
    static unsigned last_day_of_month_leap_year(unsigned m) noexcept
    {
        constexpr static unsigned char a[] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
        return a[m - 1];
    }

    // Preconditions: m is in [1, 12]
    // Returns: The number of days in the month m of year y
    // The result is always in the range [28, 31].
    template <class Int>
    static unsigned last_day_of_month(Int y, unsigned m) noexcept
    {
        return m != 2 || !is_leap(y) ? last_day_of_month_common_year(m) : 29u;
    }


    // Returns day of week in civil calendar [0, 6] -> [Sun, Sat]
    // Preconditions:  z is number of days since 1970-01-01 and is in the range:
    //                   [numeric_limits<Int>::min(), numeric_limits<Int>::max()-4].
    template <class Int>
    static unsigned weekday_from_days(Int z) noexcept
    {
        return static_cast<unsigned>(z >= -4 ? (z + 4) % 7 : (z + 5) % 7 + 6);
    }


    // Preconditions: x <= 6 && y <= 6
    // Returns: The number of days from the weekday y to the weekday x.
    // The result is always in the range [0, 6].
    static unsigned weekday_difference(unsigned x, unsigned y) noexcept
    {
        x -= y;
        return x <= 6 ? x : x + 7;
    }


    // Preconditions: wd <= 6
    // Returns: The weekday following wd
    // The result is always in the range [0, 6].
    static unsigned next_weekday(unsigned wd) noexcept
    {
        return wd < 6 ? wd + 1 : 0;
    }


    // Preconditions: wd <= 6
    // Returns: The weekday prior to wd
    // The result is always in the range [0, 6].
    static unsigned prev_weekday(unsigned wd) noexcept
    {
        return wd > 0 ? wd - 1 : 6;
    }

    // main cycle of time library
    static void update();
    // main cycle of time library
    // @param current current timestamp in millionseconds
    static void update(std::time_t current);
    // query time event queue
    static EventQueue* getTimeEventQueue();
    // get timestamp in nanoseconds from OS
    static std::time_t getNanoSecondsTimestampOS();
    // get timestamp in millionseconds from time utility library
    static std::time_t getMillionSecondsTimestamp();
    // get timestamp in millionseconds from OS
    static std::time_t getMillionSecondsTimestampOS();
    // get timestamp in seconds from OS
    static std::time_t getSecondsTimestamp();
    // get timestamp in seconds from time utility library
    static std::time_t getSecondsTimestampOS();
    // returns difference between UTC and CIVIL in seconds
    static std::time_t getUTCDifference();
    struct CIVIL {
        // get struct tm* from now(CIVIL)
        static const struct tm* get_tm();
        // returns number of days from now to 't'(CIVIL)
        static int getDays(std::time_t t);
        // returns number of days from 't1' to 't2'(CIVIL)
        static int getDays(std::time_t t1, std::time_t t2);
        // get DayTime from now(CIVIL)
        static const Datetime& getDatetime();
        // returns Datetime by 't'(CIVIL)
        static const Datetime getDatetime(std::time_t t);
        // returns timestamp in seconds from date time(CIVIL)
        static std::time_t getTimestampFromDatetime(const Datetime& datetime);
        // Is in the same day?(CIVIL)
        static bool inSameDay(std::time_t t1, std::time_t t2);
        // Is in the same day?(CIVIL), compare to now
        static bool inSameDay(std::time_t t);
        // Is in the same week?(CIVIL)
        static bool inSameWeek(std::time_t t1, std::time_t t2);
        // Is in the same week?(CIVIL), compare to now
        static bool inSameWeek(std::time_t t);
        // Is in the same month?(CIVIL)
        static bool inSameMonth(std::time_t t1, std::time_t t2);
        // Is in the same month?(CIVIL), compare to now
        static bool inSameMonth(std::time_t t);
    };
    struct UTC {
        // get struct tm* from now(UTC)
        static const struct tm* get_tmUTC();
        // get DayTime from time now(UTC)
        static const Datetime& getDatetimeUTC();
        // returns Datetime by 't'(UTC)
        static const Datetime getDatetimeUTC(std::time_t t);
        // returns number of days from now to 't'(UTC)
        static int getDaysUTC(std::time_t t);
        // returns number of days from 't1' to 't2'(UTC)
        static int getDaysUTC(std::time_t t1, std::time_t t2);
        // returns timestamp in seconds from date time(UTC)
        static std::time_t getTimestampFromDatetimeUTC(const Datetime& datetime);
        // Is in the same day?(UTC)
        static bool inSameDayUTC(std::time_t t1, std::time_t t2);
        // Is in the same day?(UTC), compare to now
        static bool inSameDayUTC(std::time_t t);
        // Is in the same week?(UTC)
        static bool inSameWeekUTC(std::time_t t1, std::time_t t2);
        // Is in the same week?(UTC), compare to now
        static bool inSameWeekUTC(std::time_t t);
        // Is in the same month?(UTC)
        static bool inSameMonthUTC(std::time_t t1, std::time_t t2);
        // Is in the same month?(UTC), compare to now
        static bool inSameMonthUTC(std::time_t t);
    };
};

}}
